package org.elsfs.auth.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.elsfs.auth.jackson2.Oauth2TokenFormatSerializer;
import org.elsfs.domain.entity.SysRegisteredClient;
import org.elsfs.domain.manager.SysRegisteredClientManager;
import org.elsfs.framework.generator.SnowFlake;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.stereotype.Service;

/**
 * 注册客户端适配器
 *
 * @author zeng
 * @since 0.0.1
 */
@Service
@RequiredArgsConstructor
public class RegisteredClientRepositoryImpl implements RegisteredClientRepository {

  private final SysRegisteredClientManager sysRegisteredClientManager;
  private static final SnowFlake SNOW_FLAKE = new SnowFlake(30);

  @Override
  public void save(RegisteredClient registeredClient) {
    sysRegisteredClientManager.createRegisteredClient(
        RegisteredClientConvert.convert(registeredClient));
  }

  @Override
  public RegisteredClient findById(String id) {
    return RegisteredClientConvert.convert(sysRegisteredClientManager.getById(id));
  }

  @Override
  public RegisteredClient findByClientId(String clientId) {
    return RegisteredClientConvert.convert(sysRegisteredClientManager.getByClientId(clientId));
  }

  private static class RegisteredClientConvert {

    private static final ObjectMapper OBJECT_MAPPER =
        Jackson2ObjectMapperBuilder.json()
            .serializerByType(OAuth2TokenFormat.class, new Oauth2TokenFormatSerializer())
            //      .modules(new OAuth2AuthorizationServerJackson2Module())
            .build();

    private static final List<String> MAC_ALGORITHM = Arrays.asList("HS256", "HS384", "HS512");
    private static final List<String> SIGNATURE_ALGORITHM =
        Arrays.asList(
            "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512");

    public static RegisteredClient convert(SysRegisteredClient client) {
      if (client.getClientId() == null) {
        client.setClientId(SNOW_FLAKE.nextId() + "");
      }

      return RegisteredClient.withId(client.getId())
          .clientId(client.getClientId())
          .clientIdIssuedAt(client.getClientIdIssuedAt())
          .clientSecret(client.getClientSecret())
          .clientSecretExpiresAt(client.getClientSecretExpiresAt())
          .clientName(client.getClientName())
          .clientAuthenticationMethods(
              clientAuthenticationMethods ->
                  clientAuthenticationMethods.addAll(
                      Arrays.stream(client.getClientAuthenticationMethods().split(","))
                          .map(ClientAuthenticationMethod::new)
                          .toList()))
          .authorizationGrantTypes(
              authorizationGrantTypes ->
                  authorizationGrantTypes.addAll(
                      Arrays.stream(client.getAuthorizationGrantTypes().split(","))
                          .map(AuthorizationGrantType::new)
                          .toList()))
          .redirectUris(s -> s.addAll(Arrays.stream(client.getRedirectUris().split(",")).toList()))
          .postLogoutRedirectUris(
              ps ->
                  ps.addAll(Arrays.stream(client.getPostLogoutRedirectUris().split(",")).toList()))
          .scopes(scopes -> scopes.addAll(Arrays.stream(client.getScopes().split(",")).toList()))
          .clientSettings(clientSettings(client.getClientSettings()))
          .tokenSettings(tokenSettings(client.getTokenSettings()))
          .build();
    }

    public static SysRegisteredClient convert(RegisteredClient client) {
      SysRegisteredClient sysRegisteredClient = null;
      try {
        sysRegisteredClient =
            new SysRegisteredClient()
                .setId(client.getId())
                .setClientId(client.getClientId())
                .setClientIdIssuedAt(client.getClientIdIssuedAt())
                .setClientSecret(client.getClientSecret())
                .setClientSecretExpiresAt(client.getClientIdIssuedAt())
                .setClientAuthenticationMethods(
                    ListToString(
                        client.getClientAuthenticationMethods().stream()
                            .map(ClientAuthenticationMethod::getValue)
                            .toList()))
                .setAuthorizationGrantTypes(
                    ListToString(
                        client.getAuthorizationGrantTypes().stream()
                            .map(AuthorizationGrantType::getValue)
                            .toList()))
                .setRedirectUris(ListToString(client.getRedirectUris().stream().toList()))
                .setPostLogoutRedirectUris(
                    ListToString(client.getPostLogoutRedirectUris().stream().toList()))
                .setScopes(ListToString(client.getScopes().stream().toList()))
                .setClientSettings(
                    OBJECT_MAPPER.writeValueAsString(client.getClientSettings().getSettings()))
                .setTokenSettings(
                    OBJECT_MAPPER.writeValueAsString(client.getTokenSettings().getSettings()));

      } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
      }

      return sysRegisteredClient;
    }

    private static ClientSettings clientSettings(String clientSettingsStrJson) {
      try {
        HashMap hashMap = OBJECT_MAPPER.readValue(clientSettingsStrJson, HashMap.class);
        ClientSettings.Builder builder = ClientSettings.builder();
        Boolean requireProofKey =
            (Boolean) hashMap.get(ConfigurationSettingNames.Client.REQUIRE_PROOF_KEY);
        Boolean requireAuthorizationConsent =
            (Boolean) hashMap.get(ConfigurationSettingNames.Client.REQUIRE_AUTHORIZATION_CONSENT);
        String jwkSetUrl = (String) hashMap.get(ConfigurationSettingNames.Client.JWK_SET_URL);
        final String tokenEndpointAuthenticationSigningAlgorithm =
            (String)
                hashMap.get(
                    ConfigurationSettingNames.Client
                        .TOKEN_ENDPOINT_AUTHENTICATION_SIGNING_ALGORITHM);
        if (requireProofKey != null) {
          builder.requireProofKey(requireProofKey);
        }
        if (requireAuthorizationConsent != null) {
          builder.requireAuthorizationConsent(requireAuthorizationConsent);
        }
        if (jwkSetUrl != null) {
          builder.jwkSetUrl(jwkSetUrl);
        }
        if (tokenEndpointAuthenticationSigningAlgorithm != null) {
          if (MAC_ALGORITHM.contains(tokenEndpointAuthenticationSigningAlgorithm)) {
            builder.tokenEndpointAuthenticationSigningAlgorithm(
                MacAlgorithm.from(tokenEndpointAuthenticationSigningAlgorithm));
          }
          if (SIGNATURE_ALGORITHM.contains(tokenEndpointAuthenticationSigningAlgorithm)) {
            builder.tokenEndpointAuthenticationSigningAlgorithm(
                SignatureAlgorithm.from(tokenEndpointAuthenticationSigningAlgorithm));
          }
        }
        return builder.build();
      } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
      }
    }

    private static TokenSettings tokenSettings(String tokenSettingsStr) {
      try {
        HashMap<String, Object> hashMap = OBJECT_MAPPER.readValue(tokenSettingsStr, HashMap.class);
        TokenSettings.Builder builder = TokenSettings.builder();
        final String authorizationCodeTimeToLive =
            hashMap.get(ConfigurationSettingNames.Token.AUTHORIZATION_CODE_TIME_TO_LIVE).toString();
        final String accessTokenTimeToLive =
            hashMap.get(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE).toString();
        final String accessTokenFormat =
            hashMap.get(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT).toString();
        final String deviceCodeTimeToLive =
            hashMap.get(ConfigurationSettingNames.Token.DEVICE_CODE_TIME_TO_LIVE).toString();
        final String reuseRefreshTokens =
            hashMap.get(ConfigurationSettingNames.Token.REUSE_REFRESH_TOKENS).toString();
        final String refreshTokenTimeToLive =
            hashMap.get(ConfigurationSettingNames.Token.REFRESH_TOKEN_TIME_TO_LIVE).toString();
        final String idTokenSignatureAlgorithm =
            hashMap.get(ConfigurationSettingNames.Token.ID_TOKEN_SIGNATURE_ALGORITHM).toString();
        if (authorizationCodeTimeToLive != null) {
          builder.authorizationCodeTimeToLive(
              Duration.ofSeconds(Double.valueOf(authorizationCodeTimeToLive).longValue()));
        }
        if (accessTokenTimeToLive != null) {
          builder.accessTokenTimeToLive(
              Duration.ofSeconds(Double.valueOf(accessTokenTimeToLive).longValue()));
        }
        if (accessTokenFormat != null) {
          builder.accessTokenFormat(new OAuth2TokenFormat(accessTokenFormat));
        }
        if (deviceCodeTimeToLive != null) {
          builder.deviceCodeTimeToLive(
              Duration.ofSeconds(Double.valueOf(deviceCodeTimeToLive).longValue()));
        }
        if (reuseRefreshTokens != null) {
          builder.reuseRefreshTokens(Boolean.parseBoolean(reuseRefreshTokens));
        }
        if (refreshTokenTimeToLive != null) {
          builder.refreshTokenTimeToLive(
              Duration.ofSeconds(Double.valueOf(refreshTokenTimeToLive).longValue()));
        }
        if (idTokenSignatureAlgorithm != null) {
          builder.idTokenSignatureAlgorithm(SignatureAlgorithm.from(idTokenSignatureAlgorithm));
        }
        return builder.build();
      } catch (JsonProcessingException e) {
        throw new RuntimeException(e);
      }
    }

    private static String ListToString(List<String> list) {
      StringBuffer s = new StringBuffer();
      for (int i = list.size() - 1; i >= 0; i--) {
        if (i == 0) {
          s.append(list.get(i));
          break;
        }
        s.append(list.get(i)).append(",");
      }
      return s.toString();
    }
  }
}
